/*
 * 
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.rt.io;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * Utils class. May utility methods used to perform file IO.
 * 
 * @author Luis Atencio
 *
 */
public class Utils {

	static final String COMMENT_CHARS = "#!";

	/** Constant for the radix of hex numbers. */
	private static final int HEX_RADIX = 16;

	/** Constant for the length of a unicode literal. */
	private static final int UNICODE_LEN = 4;


	public static URL locate(String base, String name) {
		
		if (name == null || name.length() == 0) {
			// undefined, always return null
			return null;
		}

		URL url = null;

		// attempt to create a URL directly
		try {
			if (base == null || base.length() == 0) {
				url = new URL(name);
				
			} else {
				URL baseURL = new URL(base);
                url = new URL(baseURL, name);
                
                // check if the file exists
    			InputStream in = null;
    			try {
    				in = url.openStream();
    			} finally {
    				if (in != null) {
    					in.close();
    				}
    			}
			}
		} catch (IOException e) {
			url = null;
		}

		// attempt to load from an absolute path
		if (url == null) {
			File file = new File(name);
			if (file.isAbsolute() && file.exists()) // already absolute?
			{
				try {
					url = toURL(file);
					System.out
							.println("Loading configuration from the absolute path "
									+ name);
				} catch (MalformedURLException e) {
					System.err.println("Could not obtain URL from file");
				}
			}
		}

		// attempt to load from the base directory
		if (url == null) {
			try {
				File file = constructFile(base, name);
				if (file != null && file.exists()) {
					url = toURL(file);
				}

				if (url != null) {
					System.out.println("Loading configuration from the path "
							+ file);
				}
			} catch (MalformedURLException e) {
				System.err.println("Could not obtain URL from file");
			}
		}

		// attempt to load from the user home directory
		if (url == null) {
			try {
				File file = constructFile(System.getProperty("user.home"), name);
				if (file != null && file.exists()) {
					url = toURL(file);
				}

				if (url != null) {
					System.out
							.println("Loading configuration from the home path "
									+ file);
				}

			} catch (MalformedURLException e) {
				System.err.println("Could not obtain URL from file");
			}
		}

		// attempt to load from classpath
		if (url == null) {
			url = locateFromClasspath(name);
		}
		return url;
	}

	public static boolean isCommentLine(String line) {
		String s = line.trim();
		// blanc lines are also treated as comment lines
		return s.length() < 1 || COMMENT_CHARS.indexOf(s.charAt(0)) >= 0;
	}

	public static String unescapeJava(String str, char delimiter) {
		if (str == null) {
			return null;
		}
		int sz = str.length();
		StringBuffer out = new StringBuffer(sz);
		StringBuffer unicode = new StringBuffer(UNICODE_LEN);
		boolean hadSlash = false;
		boolean inUnicode = false;
		for (int i = 0; i < sz; i++) {
			char ch = str.charAt(i);
			if (inUnicode) {
				// if in unicode, then we're reading unicode
				// values in somehow
				unicode.append(ch);
				if (unicode.length() == UNICODE_LEN) {
					// unicode now contains the four hex digits
					// which represents our unicode character
					try {
						int value = Integer.parseInt(unicode.toString(),
								HEX_RADIX);
						out.append((char) value);
						unicode.setLength(0);
						inUnicode = false;
						hadSlash = false;
					} catch (NumberFormatException nfe) {
						throw new RuntimeException(
								"Unable to parse unicode value: " + unicode,
								nfe);
					}
				}
				continue;
			}

			if (hadSlash) {
				// handle an escaped value
				hadSlash = false;

				if (ch == '\\') {
					out.append('\\');
				} else if (ch == '\'') {
					out.append('\'');
				} else if (ch == '\"') {
					out.append('"');
				} else if (ch == 'r') {
					out.append('\r');
				} else if (ch == 'f') {
					out.append('\f');
				} else if (ch == 't') {
					out.append('\t');
				} else if (ch == 'n') {
					out.append('\n');
				} else if (ch == 'b') {
					out.append('\b');
				} else if (ch == delimiter) {
					out.append('\\');
					out.append(delimiter);
				} else if (ch == 'u') {
					// uh-oh, we're in unicode country....
					inUnicode = true;
				} else {
					out.append(ch);
				}

				continue;
			} else if (ch == '\\') {
				hadSlash = true;
				continue;
			}
			out.append(ch);
		}

		if (hadSlash) {
			// then we're in the weird case of a \ at the end of the
			// string, let's output it anyway.
			out.append('\\');
		}

		return out.toString();
	}

	/**
	 * Convert the specified file into an URL. This method is equivalent to
	 * file.toURI().toURL() on Java 1.4 and above, and equivalent to
	 * file.toURL() on Java 1.3. This is to work around a bug in the JDK
	 * preventing the transformation of a file into an URL if the file name
	 * contains a '#' character. See the issue CONFIGURATION-300 for more
	 * details.
	 * 
	 * @param file
	 *            the file to be converted into an URL
	 */
	static URL toURL(File file) throws MalformedURLException {
		try {
			Method toURI = file.getClass().getMethod("toURI", (Class[]) null);
			Object uri = toURI.invoke(file, (Object[]) null);
			Method toURL = uri.getClass().getMethod("toURL", (Class[]) null);
			URL url = (URL) toURL.invoke(uri, (Object[]) null);
			return url;
		} catch (Exception e) {
			throw new MalformedURLException(e.getMessage());
		}
	}

	/**
	 * Helper method for constructing a file object from a base path and a file
	 * name. This method is called if the base path passed to
	 * <code>getURL()</code> does not seem to be a valid URL.
	 * 
	 * @param basePath
	 *            the base path
	 * @param fileName
	 *            the file name
	 * @return the resulting file
	 */
	static File constructFile(String basePath, String fileName) {
		File file = null;

		File absolute = null;
		if (fileName != null) {
			absolute = new File(fileName);
		}

		if (basePath == null || basePath.length() == 0
				|| (absolute != null && absolute.isAbsolute())) {
			file = new File(fileName);
		} else {
			StringBuffer fName = new StringBuffer();
			fName.append(basePath);

			// My best friend. Paranoia.
			if (!basePath.endsWith(File.separator)) {
				fName.append(File.separator);
			}

			//
			// We have a relative path, and we have
			// two possible forms here. If we have the
			// "./" form then just strip that off first
			// before continuing.
			//
			if (fileName.startsWith("." + File.separator)) {
				fName.append(fileName.substring(2));
			} else {
				fName.append(fileName);
			}

			file = new File(fName.toString());
		}

		return file;
	}

	static URL locateFromClasspath(String resourceName) {
		URL url = null;
		// attempt to load from the context classpath
		ClassLoader loader = Thread.currentThread().getContextClassLoader();
		if (loader != null) {
			url = loader.getResource(resourceName);

			if (url != null) {
				System.out
						.println("Loading configuration from the context classpath ("
								+ resourceName + ")");
			}
		}

		// attempt to load from the system classpath
		if (url == null) {
			url = ClassLoader.getSystemResource(resourceName);

			if (url != null) {
				System.out
						.println("Loading configuration from the system classpath ("
								+ resourceName + ")");
			}
		}
		return url;
	}

}
